<?php
// 版本1.1
namespace core;

use InvalidArgumentException;
use PDOException;
use PDOStatement;
use RuntimeException;
use PDO;

class Model
{
    // 数据库连接实例
    protected $db;

    // 表名
    protected $table;

    // 主键（默认id）
    protected $pk = 'id';

    /**  @var array 允许的查询条件类型（防止SQL注入）*/
    protected $allowedOperators = ['=', '>', '<', '>=', '<=', '!=', 'LIKE', 'IN'];

    /** @var array 存储当前查询的条件（SQL片段和参数） */
    private $queryConditions = [];

    public function __construct(?string $tableName)
    {
        // 从配置中获取数据信息（建议通过容器注入配置，此处简化）
        $config = $this->loadConfig();
        // 初始化PDO连接
        $this->initDbConnection($config);
        // 自动生成或设置表名（支持手动覆盖）
        $this->setTableName($tableName);
    }

    /**
     * 加载数据库配置（示例：从文件读取，实际建议通过容器注入）
     * @throws \RuntimeException
     * @return array
     */
    protected function loadConfig()
    {
        $configPath = __DIR__ . '/../app/config.php';
        if (!file_exists($configPath)) {
            throw new RuntimeException("数据库配置不存在：{$configPath}");
        }

        return require $configPath;
    }

    /**
     * 初始化PDO连接（分离连接逻辑，便于测试）
     * @param array $config
     * @throws \RuntimeException
     * @return void
     */
    protected function initDbConnection(array $config): void
    {
        try {
            $dsn = "mysql:host={$config['db_host']};dbname={$config['db_name']};charset=utf8mb4";
            $this->db = new PDO(
                $dsn,
                $config['db_user'],
                $config['db_pass'],
                [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                    PDO::ATTR_EMULATE_PREPARES => false
                ]
            );
        } catch (PDOException $e) {
            throw new RuntimeException("数据库连接失败：{$e->getMessage()}", 0, $e);
        }
    }

    /**
     * 设置数据表名（手动指定优先）
     * @param mixed $tableName
     * @throws \InvalidArgumentException
     * @return void
     */
    protected function setTableName(?string $tableName = null): void
    {
        if ($tableName) {
            // 手动指定表名时，仅允许字母、数字、下划线
            if (!preg_match('/^[\w]+$/', $tableName)) {
                throw new InvalidArgumentException("表名包含非法字符，仅允许字母、数字、下划线");
            }
            $this->table = $tableName;
            return;
        }

        // 自动生成表名 （类名转小写下划线 + 可选前缀）
        $className = basename(str_replace('\\', '/', get_class($this)));
        $this->table = $this->camelizeToSnake($className);
    }

    /**
     * 驼峰命名转下划线命名（修复原正则问题）
     * @param string $str
     * @return string
     */
    protected function camelizeToSnake(string $str): string
    {
        return strtolower(preg_replace('/(?<=\w)(?=[A-Z])/', '_', $str));
    }

    /**
     * 通用查询构建器（支持链式调用）
     * @param array $conditions 查询条件（格式：['字段 操作符' => 值] 或 ['字段' => 值]）
     * @return Model
     */
    public function where(array $conditions): self
    {

        $parsed = $this->parseConditions($conditions);

        // 合并到当前查询条件（支持多次where调用，使用AND连接）
        $this->queryConditions = array_merge(
            $this->queryConditions,
            ['sql' => $parsed['sql'], 'params' => $parsed['params']]
        );

        return $this;
    }

    /**
     * 解析查询条件（防止SQL注入）
     * @param array $conditions
     * @return void
     */
    protected function parseConditions(array $conditions): array
    {
        $sql = [];
        $params = [];

        try {
            foreach ($conditions as $key => $value) {
                // 支持操作符（如'age >' => 18 或 'name LIKE' => '%张三%'）
                $operator = '=';
                $field = $key;

                // 验证操作符
                if (strpos($key, ' ') !== false) {
                    list($field, $operator) = explode(' ', $key, 2);
                    if (!in_array(strtoupper($operator), $this->allowedOperators)) {
                        throw new InvalidArgumentException("不支持的操作符：{$operator}");
                    }
                }

                // 字段名安全校验（仅允许字母、数字、下划线）
                if (!preg_match('/^[\w]+$/', $field)) {
                    throw new InvalidArgumentException("字段名包含非法字符：{$field}");
                }

                $placeholder = ":{$field}_select_" . count($params);
                $sql[] = "`{$field}` {$operator} {$placeholder}";
                $params[$placeholder] = $value;
            }
        } catch (InvalidArgumentException $e) {
            echo $e->getMessage();
        }
        return ['sql' => implode(' AND ', $sql), 'params' => $params];
    }


    /**
     * 获取单条记录（支持条件查询）
     * @param mixed $conditions
     * @return array|null|bool
     */
    public function find($conditions = null)
    {
        if ($conditions === null) {
            // 无条件查询（危险！默认取第一条）
            $sql = "SELECT * FROM `$this->table` LIMIT 1";

            $params = [];
        } elseif (is_numeric($conditions)) {
            // 根据主键查询
            $sql = "SELECT * FROM `$this->table` WHERE `$this->pk` = :id LIMIT 1";

            $params = [':id' => $conditions];
        } else {
            // 条件查询（调用where方法）
            $this->where($conditions);

        }
        // 拼接WHERE子句（如果有条件）
        $whereClause = $this->queryConditions['sql'] ? " WHERE {$this->queryConditions['sql']}" : "";
        $sql = "SELECT * FROM `{$this->table}`{$whereClause} LIMIT 1";
        $params = $this->queryConditions['params'];

        return $this->query($sql, $params)->fetch();
    }

    public function select(?int $page = null, int $pageSize = 10, string $orderBy = 'id ASC')
    {
        $offset = $page ? ($page - 1) * $pageSize : 0;
        // 拼接WHERE子句（如果有条件）
        $whereClause = $this->queryConditions['sql'] ? " WHERE {$this->queryConditions['sql']}" : "";
        $sql = "SELECT * FROM `{$this->table}`{$whereClause}";
        $params = $this->queryConditions['params'];

        // 添加排序
        if (!empty($orderBy)) {
            $sql .= " ORDER BY {$orderBy}";
        }

        // 添加分页
        if ($page) {
            $sql .= " LIMIT :offset, :pageSize";
        }


        if ($page) {
            $params[':offset'] = $offset;
            $params[':pageSize'] = $pageSize;
        }

        return $this->query($sql, $params)->fetchAll();
    }



    /**
     * 插入记录（返回自增ID或影响行数）
     * @param array $data
     * @return int|false
     */
    public function insert($data)
    {
        $keys = array_keys($data);
        $placeholders = ':' . implode(', :', $keys);
        $sql = "INSERT INTO `{$this->table}` (`" . implode('`, `', $keys) . "`) VALUES ({$placeholders})";

        $this->query($sql, $data);
        return $this->db->lastInsertId();
    }

    /**
     * 更新记录（返回影响行数）
     * @param array $data 更新数据
     * @param array $conditions 条件（格式同find）
     * @return int 影响行数（0表示未更新）
     */
    public function update(array $data, array $conditions): int
    {
        $where = $this->parseConditions(is_numeric(key($conditions)) ? ['id' => key($conditions)] : $conditions);
        $set = $this->parseSetClause($data);

        $sql = "UPDATE `{$this->table}` SET {$set} WHERE {$where['sql']}";
        $params = array_merge($where['params'], $data);

        $stmt = $this->query($sql, $params);
        return $stmt->rowCount();
    }

    /**
     * 解析SET子句（防止字段名注入）
     * @param array $data
     * @return string
     */
    protected function parseSetClause(array $data): string
    {
        $set = [];
        foreach ($data as $field => $value) {
            if (!preg_match('/^[\w]+$/', $field)) {
                throw new InvalidArgumentException("字段名包含非法字符：{$field}");
            }

            $set[] = "`{$field}` = :update_{$field}";
        }
        return implode(', ', $set);
    }


    /**
     * 删除记录（返回影响行数）
     * @param array|int $conditions 主键ID 或 条件
     * @return int 影响行数（0表示未删除）
     */
    public function delete($conditions): int
    {
        if (is_numeric($conditions)) {
            $sql = "DELETE FROM `{$this->table}` WHERE `{$this->pk}` = :id";
            $params = [':id' => $conditions];
        } else {
            $where = $this->parseConditions($conditions);
            $sql = "DELETE FROM `{$this->table}` WHERE {$where['sql']}";
            $params = $where['params'];
        }

        $stmt = $this->query($sql, $params);
        return $stmt->rowCount();
    }

    /**
     * 通用预处理查询（返回PDOStatement）
     * @param string $sql
     * @param array $params
     * @return PDOStatement
     */
    public function query(string $sql, array $params = []): PDOStatement
    {
        try {
            $stmt = $this->db->prepare($sql);
            $stmt->execute($params);
            return $stmt;
        } catch (PDOException $e) {
            throw new RuntimeException("查询失败: {$e->getMessage()}, SQL: {$sql}, 参数: " . json_encode($params), 0, $e);
        }
    }
    /**
     * 分页查询
     * @param int $offset   偏移量
     * @param int $limit    查询数量
     * @return Model
     */
    public function limit($offset = 0, $limit): self
    {
        return $this;
    }

    /**
     * 排序查询
     * @param string $order 排序根据的字段（默认id）
     * @param string $by    排序规则（默认ASC）
     * @return Model
     */
    public function order($order, $by): self
    {
        return $this;
    }

    /**
     * 开启事务
     */
    public function startTrans(): void
    {
        $this->db->beginTransaction();
    }

    /**
     * 提交事务
     */
    public function commit(): void
    {
        $this->db->commit();
    }

    /**
     * 回滚事务
     */
    public function rollback(): void
    {
        $this->db->rollBack();
    }


    /**
     * 获取最后执行的SQL(调试用)
     * @return string
     */
    public function getLastSql(): string
    {
        return $this->db->query("SELECT LAST_INSERT_ID()")->fetchColumn() ?? '';
    }
}

?>